/*
 ------------------------------------------------------------------------------
 Copyright (C) 2006-2007 Team Blur.

 This file is part of the Quantum Engine source code.

 The Quantum Engine source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The Quantum Engine source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the Quantum Engine source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


// r_table.cpp -- material table script parsing and loading


#include "r_local.h"


#define TABLES_HASH_SIZE				MAX_TABLES / 4

static table_t *	r_tablesHashTable[TABLES_HASH_SIZE];
static table_t *	r_tables[MAX_TABLES];
static int			r_numTables;


/*
 ==============================================================================

 TABLE PARSING & LOADING

 ==============================================================================
*/


/*
 ==================
 R_LoadTable
 ==================
*/
static void R_LoadTable (const char *name, bool clamp, bool snap, int size, float *values){

	table_t	*table;
	uint	hashKey;

	if (r_numTables == MAX_TABLES)
		Com_Error(false, "R_LoadTable: MAX_TABLES hit");

	r_tables[r_numTables++] = table = (table_t *)Mem_Alloc(sizeof(table_t), TAG_RENDERER);

	// Fill it in
	Str_Copy(table->name, name, sizeof(table->name));
	table->index = r_numTables - 1;
	table->clamp = clamp;
	table->snap = snap;
	table->size = size;

	table->values = (float *)Mem_Alloc(size * sizeof(float), TAG_RENDERER);
	memcpy(table->values, values, size * sizeof(float));

	// Add to hash table
	hashKey = Str_HashKey(table->name, TABLES_HASH_SIZE, false);

	table->nextHash = r_tablesHashTable[hashKey];
	r_tablesHashTable[hashKey] = table;
}

/*
 ==================
 R_FindTable
 ==================
*/
table_t *R_FindTable (const char *name){

	table_t	*table;
	uint	hashKey;

	// See if already loaded
	hashKey = Str_HashKey(name, TABLES_HASH_SIZE, false);

	for (table = r_tablesHashTable[hashKey]; table; table = table->nextHash){
		if (!Str_ICompare(table->name, name))
			return table;
	}

	// Not found
	return NULL;
}

/*
 ==================
 R_ParseTable
 ==================
*/
bool R_ParseTable (script_t *script){

	table_t	*table;
	token_t	token;
	char	name[MAX_OSPATH];
	bool	clamp = false, snap = false;
	int		size = 0;
	float	values[MAX_TABLE_SIZE];

	// Parse the name
	if (!PS_ReadToken(script, &token)){
		Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for 'table'\n");
		return false;
	}

	Str_Copy(name, token.string, sizeof(name));

	// Parse the table
	if (!PS_ExpectTokenString(script, &token, "{")){
		Com_Printf(S_COLOR_YELLOW "WARNING: expected '{', found '%s' instead in table '%s'\n", token.string, name);
		return false;
	}

	while (1){
		if (!PS_ReadToken(script, &token)){
			Com_Printf(S_COLOR_YELLOW "WARNING: no concluding '}' in table '%s'\n", name);
			return false;	// End of data
		}

		if (!Str_Compare(token.string, "}"))
			break;			// End of table

		// Parse the values
		if (!Str_Compare(token.string, "{")){
			// Check if already defined
			if (size){
				Com_Printf(S_COLOR_YELLOW "WARNING: values already defined for table '%s'\n", name);
				return false;
			}

			// Parse them
			while (1){
				if (size){
					if (!PS_ReadToken(script, &token)){
						Com_Printf(S_COLOR_YELLOW "WARNING: no matching '}' in table '%s'\n", name);
						return false;	// End of data
					}

					if (!Str_Compare(token.string, "}"))
						break;			// End of values

					// It must be a comma
					if (Str_Compare(token.string, ",")){
						Com_Printf(S_COLOR_YELLOW "WARNING: expected ',', found '%s' instead in table '%s'\n", token.string, name);
						return false;
					}
				}

				// Parse the value
				if (size == MAX_TABLE_SIZE){
					Com_Printf(S_COLOR_YELLOW "WARNING: MAX_TABLE_SIZE hit in table '%s'\n", name);
					return false;
				}

				if (!PS_ReadFloat(script, &values[size])){
					Com_Printf(S_COLOR_YELLOW "WARNING: missing parameters for table '%s'\n", name);
					return false;
				}

				size++;
			}

			continue;
		}

		// Parse the flags
		if (!Str_ICompare(token.string, "clamp"))
			clamp = true;
		else if (!Str_ICompare(token.string, "snap"))
			snap = true;
		else {
			Com_Printf(S_COLOR_YELLOW "WARNING: unknown parameter '%s' in table '%s'\n", token.string, name);
			return false;
		}
	}

	// Check if no values
	if (!size){
		Com_Printf(S_COLOR_YELLOW "WARNING: values not defined for table '%s'\n", name);
		return false;
	}

	// If the table already exists, replace its parameters
	table = R_FindTable(name);
	if (table){
		Mem_Free(table->values);

		table->clamp = clamp;
		table->snap = snap;
		table->size = size;

		table->values = (float *)Mem_Alloc(size * sizeof(float), TAG_RENDERER);
		memcpy(table->values, values, size * sizeof(float));

		return true;
	}

	// Load it in
	R_LoadTable(name, clamp, snap, size, values);

	return true;
}


// ============================================================================


/*
 ==================
 R_LookupTable
 ==================
*/
float R_LookupTable (int index, float lutIndex){

	table_t	*table;
	uint	oldIndex, curIndex;

	if (index < 0 || index >= r_numTables)
		Com_Error(false, "R_LookupTable: index out of range");

	table = r_tables[index];

	lutIndex *= table->size;

	oldIndex = M_FtolFast(lutIndex);
	curIndex = M_FtolFast(lutIndex) + 1;

	if (table->clamp){
		oldIndex = Min(oldIndex, table->size - 1);
		curIndex = Min(curIndex, table->size - 1);
	}
	else {
		oldIndex %= table->size;
		curIndex %= table->size;
	}

	if (table->snap)
		return table->values[oldIndex];
	else
		return table->values[oldIndex] + (table->values[curIndex] - table->values[oldIndex]) * M_Frac(lutIndex);
}

/*
 ==================
 R_ListTables_f
 ==================
*/
static void R_ListTables_f (void){

	table_t	*table;
	int		i;

	Com_Printf("\n");
	Com_Printf("      flags size -name--------\n");

	for (i = 0; i < r_numTables; i++){
		table = r_tables[i];

		Com_Printf("%4i: ", i);

		if (table->clamp)
			Com_Printf("cl ");
		else
			Com_Printf("   ");

		if (table->snap)
			Com_Printf("sn ");
		else
			Com_Printf("   ");

		Com_Printf("%4i ", table->size);

		Com_Printf("%s\n", table->name);
	}

	Com_Printf("------------------------------\n");
	Com_Printf("%i total tables\n", r_numTables);
	Com_Printf("\n");
}

/*
 ==================
 R_InitTables
 ==================
*/
void R_InitTables (void){

	// Register our commands
	Cmd_AddCommand("listTables", R_ListTables_f, "Lists loaded tables");
}

/*
 ==================
 R_ShutdownTables
 ==================
*/
void R_ShutdownTables (void){

	// Unregister our commands
	Cmd_RemoveCommand("listTables");

	// Clear table list
	memset(r_tablesHashTable, 0, sizeof(r_tablesHashTable));
	memset(r_tables, 0, sizeof(r_tables));

	r_numTables = 0;
}
